#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/io.h> //ioremap
#include <linux/uaccess.h>

/*********************************
 * 
 * mmap 好像不能在内核驱动中调用
 *  
 * ***********************************/


//记录各个寄存器的内核虚拟地址
static void *gpiobase;
static unsigned long *gpiocout;    //数据寄存器内核虚拟地址
static unsigned long *gpiocoutenb; //输出使能寄存器内核虚拟地址
static unsigned long *gpiocaltfn0; //复用功能选择寄存器的内核虚拟地址

#define GPIO_Base_addr 0xFED0E000
#define GPIO_Page_size 4096
#define GPIO31_CFG_ADDR 0x330 //供电检测引脚
#define GPIO31_PORT_ADDR (GPIO31_CFG_ADDR + 8)
#define GPIO_CFG_Value 0x2003CD00
#define GPIO_PORT_CFG_Value 0x00000003

#define LED_ON 0x100001  //开灯命令
#define LED_OFF 0x100002 //关灯命令

static long voltage_control_ioctl(struct file *file,
                                  unsigned long cmd,
                                  unsigned long arg)
{
    //1.分配内核缓冲区
    int kindex;

    //2.拷贝用户缓冲区数据到内核缓冲区
    copy_from_user(&kindex, (int *)arg, sizeof(kindex));

    //3.解析用户发送来的命令
    switch (cmd)
    {
    case LED_ON:
        if (kindex == 1)
            *gpiocout &= ~(1 << 12);
        /*
            else if(kindex == 2)
                ....
            else if(kindex == 3)
                ....
            else if(kindex == 4)
                ....
            */
        break;
    case LED_OFF:
        if (kindex == 1)
            *gpiocout |= (1 << 12);
        /*
            else if(kindex == 2)
                ....
            else if(kindex == 3)
                ....
            else if(kindex == 4)
                ....
            */
        break;
    default:
        return -1;
    }

    //4.添加调试打印信息,将操作的寄存器的值打印出来
    //如果将来发现寄存器的值是对的,但是灯没有反应
    //请问：是谁的问题？此问题势必是硬件问题
    // printk("GPIOCALTFN0=%#x, GPIOCOUTENB=%#x, GPIOCOUT=%#x\n", *gpiocaltfn0, *gpiocoutenb, *gpiocout);
    printk("%d\n",(*((int *)(mem + GPIO31_PORT_ADDR)) & 0x01));

    return 0;
}

//定义初始化硬件操作接口对象
static struct file_operations voltage_control_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = voltage_control_ioctl};

//定义初始化混杂设备对象
static struct miscdevice voltage_control_misc = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "my_voltage_control",
    .fops = &voltage_control_fops};

static int voltage_control_init(void)
{
    //1.注册混杂设备到内核
    misc_register(&voltage_control_misc);

#if 0
    //2.将各个寄存器的物理地址映射到内核虚拟地址
    //一旦完成映射,驱动访问映射的内核虚拟地址就是
    //在访问对应的物理地址
    gpiobase = ioremap(0xC001C000, 0x24);
    gpiocout = (unsigned long *)(gpiobase + 0x00);
    gpiocoutenb = (unsigned long *)(gpiobase + 0x04);
    gpiocaltfn0 = (unsigned long *)(gpiobase + 0x20);
    
    //3.配置引脚为GPIO功能,配置为输出,输出1(省电)
    *gpiocaltfn0 &= ~(0x3 << 24);
    *gpiocaltfn0 |= (1 << 24);
    *gpiocoutenb |= (1 << 12);
    *gpiocout |= (1 << 12);
#endif

    uint8_t Value, fd;
    char *mem;
    uint64_t TMP = 0, i = 0;
    uint8_t copy_action[100];

    //open /dev/mem with read and write mode
    if ((fd = open("/dev/mem", O_RDWR | O_SYNC)) < 0)
    {
        perror("open error");
        return -1;
    }

    //map physical memory 0-10 bytes
    mem = mmap(0, GPIO_Page_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, GPIO_Base_addr);
    if (mem == MAP_FAILED)
    {
        perror("mmap error:");
        return 1;
    }

    TMP = GPIO_CFG_Value;
    memcpy((mem + GPIO31_CFG_ADDR), &TMP, 4);
    TMP = GPIO_PORT_CFG_Value;
    memcpy((mem + GPIO31_PORT_ADDR), &TMP, 4);

    return 0;
}

static void voltage_control_exit(void)
{
#if 0
    // 1.输出1,解除地址映射
    *gpiocout |= (1 << 12);
    iounmap(gpiobase);
#endif


    //2.卸载混杂设备
    misc_deregister(&voltage_control_misc);
}
module_init(voltage_control_init);
module_exit(voltage_control_exit);
MODULE_LICENSE("GPL");
